package mac10.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import mac10.connection.ActionFactory;
import mac10.connection.MessageConstants;
import mac10.messages.OrderMessage;
import mac10.messages.PerceptionMessage;
import mac10.messages.PerceptionsFinishedMessage;
import mac10.messages.RegisterMessage;
import mac10.messages.SimEndMessage;
import mac10.messages.SimStartMessage;
import mac10.models.Agent;
import mac10.models.Cell;
import mac10.models.WorldModel;
import mac10.monitor.InfoMonitor;
import mac10.roles.AbstractRole;
import mac10.roles.Explorer;
import mac10.roles.MasterSwitcher;
import mac10.roles.Shepherd;
import mac10.roles.Switcher;

import org.sercho.masp.space.event.SpaceEvent;
import org.sercho.masp.space.event.SpaceObserver;
import org.sercho.masp.space.event.WriteCallEvent;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import de.dailab.jiactng.agentcore.IAgentBean;
import de.dailab.jiactng.agentcore.action.AbstractMethodExposingBean;
import de.dailab.jiactng.agentcore.action.Action;
import de.dailab.jiactng.agentcore.comm.IMessageBoxAddress;
import de.dailab.jiactng.agentcore.comm.message.IJiacMessage;
import de.dailab.jiactng.agentcore.comm.message.JiacMessage;
import de.dailab.jiactng.agentcore.knowledge.IFact;
import de.dailab.jiactng.agentcore.lifecycle.LifecycleException;
import de.dailab.jiactng.agentcore.ontology.AgentDescription;
import de.dailab.jiactng.agentcore.ontology.IAgentDescription;

/**
 * The <code>PerceptionBean</code> handles the perception, checks action
 * success, triggers decisions, communicates with team mates.
 * 
 * @author axle
 * @see de.dailab.jiactng.agentcore.AbstractMethodExposingBean
 */
public class AgentBean extends AbstractMethodExposingBean implements
        MessageConstants
{

    private String username;
    private int positionX;
    private int positionY;
    private WorldModel worldModel = new WorldModel(0, 0, 0, 0, 0, 0, 0, 0);
    private String actionID = null;
    private int step;
    private long actionDeadline;
    private boolean simulationRunning = false;
    private AbstractRole currentRole;
    private boolean registeredWithMasterAgent = false,
            anouncedSimStart = false;
    private SimStartMessage simStartMsg;
    private ServerCommunicationBean serverCommunicationBean;
    private IMessageBoxAddress masterAgentAdress = null;

    /** Actions to communicate with other team members. */
    private Action sendMessageAction;

    public ServerCommunicationBean getServerCommunicationBean ()
    {
        return serverCommunicationBean;
    }

    public void setServerCommunicationBean (
            ServerCommunicationBean serverCommunicationBean)
    {
        this.serverCommunicationBean = serverCommunicationBean;
    }

    public void setUsername (String username)
    {
        this.username = username;
    }

    public String getUsername ()
    {
        return username;
    }

    @Override
    public void doInit () throws Exception
    {
        super.doInit();

        for (IAgentBean ab : thisAgent.getAgentBeans())
        {
            if (ab instanceof ServerCommunicationBean)
            {
                this.serverCommunicationBean = (ServerCommunicationBean) ab;
            }
        }
    }

    @Override
    public void doStart () throws Exception
    {
        super.doStart();

        // Retrieve my own send action
        sendMessageAction = retrieveAction("de.dailab.jiactng.agentcore.comm.ICommunicationBean#send");

        // If no send action is available, check your agent configuration.
        // CommunicationBean is needed
        if (sendMessageAction == null)
            throw new RuntimeException("cannot send - no local action exists");

        // register with master agent
        // log.info("registering with master agent");

        new RegisterMessage(this.thisAgent
                .getAgentDescription());
        memory.attach(new MessageObserver(), new JiacMessage());
    }

    /**
     * Implement message parser and so in starting here.
     * 
     * @param message
     */
    public void processServerMessage (Document message)
    {

        Element root = message.getDocumentElement();

        try
        {
            Long.parseLong(root.getAttribute("timestamp"));
        }
        catch (NumberFormatException e)
        {
            // ignore it
        }
        // System.out.println(timestamp);

        String type = root.getAttribute("type").trim();
        if (type.equalsIgnoreCase(MessageConstants.MESSAGE_ACTION_REQUEST))
        {
            processActionRequest(root);
        }
        else if (type.equalsIgnoreCase(MessageConstants.MESSAGE_AUTH_RESPONSE))
        {
            processAuthenticationResponse(root);
        }
        else if (type.equalsIgnoreCase(MessageConstants.MESSAGE_SIM_START))
        {
            processSimulationStart(root);
        }
        else if (type.equalsIgnoreCase(MessageConstants.MESSAGE_SIM_END))
        {
            processSimulationEnd(root);
        }
        else if (type.equalsIgnoreCase(MessageConstants.MESSAGE_BYE))
        {
            processBye(root);
        }

    }

    /**
     * Send the action to contest server.
     * 
     * @param action
     *            the action to send (one of {@link MessageConstants}
     */
    public void sendActionResponse (String action, String perceptionId)
    {

        Document response = ActionFactory.getAction(action, perceptionId);
        
        InfoMonitor.staticMonitor.setAgentPosition("(" + positionX + ","
                + positionY + ")", thisAgent.getAgentName());
        
        InfoMonitor.staticMonitor.setAgentText(action, thisAgent.getAgentName());
        
        //System.out.println(thisAgent.getAgentName() + action);
        
        
        serverCommunicationBean.sendActionResponse(response);
    }

    /**
     * Creates the authentication response.
     * 
     * @param message
     *            the message
     * 
     * @return the authentication response
     */
    private void processAuthenticationResponse (Element message)
    {
        NodeList nodes = message.getElementsByTagName("authentication");
        Element childPerception = (Element) nodes.item(0);

        String authResult = childPerception.getAttribute("result");
        log.warn("authentication result received: " + authResult);

    }

    /**
     * Creates the simulation start.
     * 
     * @param message
     *            the message
     * 
     * @return the simulation start
     */
    private void processSimulationStart (Element message)
    {
        NodeList nodes = message.getElementsByTagName("simulation");
        Element childPerception = (Element) nodes.item(0);

        childPerception.getAttribute("id");
        childPerception.getAttribute("opponent");
        int steps = Integer.parseInt(childPerception.getAttribute("steps"));
        int gsizex = Integer.parseInt(childPerception.getAttribute("gsizex"));
        int gsizey = Integer.parseInt(childPerception.getAttribute("gsizey"));
        int corralx0 = Integer.parseInt(childPerception
                .getAttribute("corralx0"));
        int corraly0 = Integer.parseInt(childPerception
                .getAttribute("corraly0"));
        int corralx1 = Integer.parseInt(childPerception
                .getAttribute("corralx1"));
        int corraly1 = Integer.parseInt(childPerception
                .getAttribute("corraly1"));
        int lineOfSight = Integer.parseInt(childPerception
                .getAttribute("lineOfSight"));
        this.worldModel = new WorldModel(gsizex, gsizey, steps, lineOfSight,
                corralx0, corraly0, corralx1, corraly1);
        // log.warn("Simulation start message received with data:" + " id=" + id
        // + " opponent=" + opponent + " steps=" + steps + " gridSizeX=" +
        // gsizex + " gridSizeY=" + gsizey + " corralX0=" + corralx0 +
        // " corralY0=" + corraly0 + " corralX1=" + corralx1 + " corralY1=" +
        // corraly1 + " lineOfSight=" + lineOfSight);
        this.simStartMsg = new SimStartMessage(this.thisAgent
                .getAgentDescription(), message);
        this.setExecuteInterval(100000);
        this.simulationRunning = true;
        this.setNextExecutionTime(System.currentTimeMillis() + 200);
    }

    /**
     * Creates the simulation end.
     * 
     * @param message
     *            the message
     * 
     * @return the simulation end
     */
    private void processSimulationEnd (Element message)
    {
        NodeList nodes = message.getElementsByTagName("sim-result");
        Element childPerception = (Element) nodes.item(0);

        String result = childPerception.getAttribute("result");
        double score = Double.parseDouble(childPerception.getAttribute("averageScore"));
        log.warn("Simulation end message received: result=" + result
                + " score=" + score);
        sendMessageToMasterAgent(new SimEndMessage(this.thisAgent
                .getAgentDescription(), message));
        this.simulationRunning = false;
        this.anouncedSimStart = false;
        this.worldModel = null;
        this.actionID = "";
    }

    /**
     * Creates the perception.
     * 
     * @param message
     *            the message
     * 
     * @return the perception
     */
    private void processActionRequest (Element message)
    {
        NodeList nodes = message.getElementsByTagName("perception");
        Element childPerception = (Element) nodes.item(0);
        long deadline = Long
                .parseLong(childPerception.getAttribute("deadline"));
        
        this.actionDeadline = deadline;
        String id = childPerception.getAttribute("id");
        this.actionID = id;
        int step = Integer.parseInt(childPerception.getAttribute("step"));
        this.step = step;
        this.positionX = Short.parseShort(childPerception.getAttribute("posx"));
        this.positionY = Short.parseShort(childPerception.getAttribute("posy"));
        
        Agent agent = new Agent();
        agent.name = username;
        agent.x = positionX;
        agent.y = positionY;
        WorldModel.staticWorldModel.updateAgents(agent);
        
        int cowsInCorral = Integer.parseInt(childPerception.getAttribute("cowsInCorral"));

        ArrayList<Cell> cellList = new ArrayList<Cell>();
        NodeList cells = childPerception.getElementsByTagName("cell");
        new StringBuffer();
        if (cells != null)
        {
            for (int i = 0; i < cells.getLength(); i++)
            {
                Element element = (Element) cells.item(i);
                Cell cell = Cell.create(element, this.positionX,
                        this.positionY, step);
                if (cell != null)
                    cellList.add(cell);
            }
        }
        // log.info("i see cells: " + cellList.size());
        PerceptionMessage pMessage = new PerceptionMessage(this.thisAgent
                .getAgentDescription(), cellList, step, this.positionX,
                this.positionY, cowsInCorral, this.actionDeadline);
        // sendMessageToMasterAgent(pMessage);
        this.setNextExecutionTime(this.actionDeadline - 200);
        long shortestDeadline = WorldModel.staticWorldModel
                .processPerception(pMessage);
        MasterBean.agents.put(this.thisAgent.getAgentDescription(), new Cell(
                this.positionX, this.positionY, -1));
        if (shortestDeadline != -1)
        {
            sendMessageToMasterAgent(new PerceptionsFinishedMessage(step,
                    shortestDeadline));
        }
    }

    private void processBye (Element message)
    {
        log.warn("Bye message received");
        try
        {
            this.thisAgent.stop();
        }
        catch (LifecycleException e)
        {
            e.printStackTrace();
        }
    }

    private void processOrder (OrderMessage message)
    {
        this.worldModel = WorldModel.staticWorldModel;
//        if (message.getStep() != this.step
//                || System.currentTimeMillis() > this.actionDeadline)
//            return;
        // log.info(this.thisAgent.getAgentName() + ": received order " +
        // (this.actionDeadline-System.currentTimeMillis()) +
        // "ms before deadline...");
        String role = message.getRole();
        String task = message.getTask();
        
        if (this.currentRole == null) {
            this.currentRole = new Explorer(this.thisAgent
                    .getAgentDescription(), this.worldModel);
        }
        
        if (!this.currentRole.toString().equals(role))
        {
//            System.err.println(username+ ": change role from "
//                    + this.currentRole.toString() + " to " + role);
            
            MasterSwitcher.staticMasterSwitcher.deregister(this.thisAgent.getAgentDescription());
            
            if (role.equals("explorer"))
                this.currentRole = new Explorer(this.thisAgent
                        .getAgentDescription(), this.worldModel);
            if (role.equals("switcher"))
                this.currentRole = new Switcher(this.thisAgent
                        .getAgentDescription(), this.worldModel);
            if (role.equals("shepherd"))
                this.currentRole = new Shepherd(this.thisAgent
                        .getAgentDescription(), this.worldModel);
            
        }
        else
        {
            this.currentRole.advanceStep(this.step);
        }
        
        //System.err.println("\t\t"+username+" "+task);
        
        //long start = System.currentTimeMillis();
        String action = this.currentRole.executeTask(this.worldModel, task,
                this.positionX, this.positionY, System.currentTimeMillis()+2000);
        //long end = System.currentTimeMillis();
        
        // System.err.println("\t\t\t"+username+" "+(end-start));
        
        sendActionResponse(action, this.actionID);
    }

    private void obtainMasterAgentAdress ()
    {
        // retrieve all agents
        List<IAgentDescription> agentDescriptions = thisAgent
                .searchAllAgents(new AgentDescription());

        String ownMasterAgent = "";
        if (this.username.startsWith("b"))
            ownMasterAgent = "MasterAgentB";
        else
            ownMasterAgent = "MasterAgentA";
        // search MasterAgent
        for (IAgentDescription agent : agentDescriptions)
        {
            if (agent.getName().startsWith(ownMasterAgent))
            {
                this.masterAgentAdress = agent.getMessageBoxAddress();
            }
        }
    }

    private void registerWithMasterAgent ()
    {
        // invoke(sendMessageAction, new Serializable[] {
        // new JiacMessage(this.registerMsg), this.masterAgentAdress });
        MasterBean.agents.put(this.thisAgent.getAgentDescription(), null);
        MasterBean.sortedAgents.add(this.thisAgent.getAgentDescription());
        this.registeredWithMasterAgent = true;
    }

    private void anounceSimStart ()
    {
        invoke(sendMessageAction, new Serializable[] {
                new JiacMessage(this.simStartMsg), this.masterAgentAdress });
        this.anouncedSimStart = true;
    }

    private void sendMessageToMasterAgent (IFact fact)
    {
        if (this.masterAgentAdress == null)
            return;

        // log.info("sending message to master agent");
        invoke(sendMessageAction, new Serializable[] { new JiacMessage(fact),
                this.masterAgentAdress });
    }

    @Override
    public void execute ()
    {
//        if (this.simulationRunning)
//        {
//            if (this.receivedOrderThisStep)
//                return;
//            log.warn(this.thisAgent.getAgentName()
//                    + ": no order received for step " + this.step
//                    + "...continue task...");
//            if (this.currentRole == null)
//                this.currentRole = new Explorer(this.thisAgent
//                        .getAgentDescription(), this.worldModel);
//            String action = this.currentRole.executeTask(this.worldModel,
//                    this.currentRole.getLastTask(), this.positionX,
//                    this.positionY, this.actionDeadline);
//            sendActionResponse(action, this.actionID);
//        }

        if (this.masterAgentAdress == null)
        {
            obtainMasterAgentAdress();
            if (this.masterAgentAdress == null)
                return;
        }
        if (!this.registeredWithMasterAgent)
            registerWithMasterAgent();
        if (this.simulationRunning && !this.anouncedSimStart)
            anounceSimStart();
    }

    // this observer checks for memory events (see tuplespace docu for more
    // info)
    private class MessageObserver implements SpaceObserver<IFact>
    {

        private static final long serialVersionUID = 1L;

        @SuppressWarnings("unchecked")
		public void notify (SpaceEvent<? extends IFact> event)
        {
            // check only for write events in memory
            if (event instanceof WriteCallEvent<?>)
            {
                WriteCallEvent<IJiacMessage> wce = (WriteCallEvent<IJiacMessage>) event;

                // consume message
                IJiacMessage message = memory.remove(wce.getObject());
                // log.info(message);
                if (message.getPayload() instanceof OrderMessage) {
                    processOrder((OrderMessage) message.getPayload());
                }
//                if (message.getPayload() instanceof WorldModel)
//                    processWorldModelUpdate((WorldModel) message.getPayload());
            }
        }

    }
}